/*
 * Systemprogrammierung
 * Multiplayer-Quiz
 *
 * Gemeinsam verwendete Module
 *
 * sockets.c: Die Socket-Funktionen zum Senden und Empfangen
 */

#include "sockets.h"

/**
 * \brief 	Erzeugt Socket für Client-Prozess zur Kommunikation mit Server
 *
 * \param 	args enthält Host-IP und Port zu dem Verbindung hergestellt werden soll
 *
 * \return	Socket bei Erfolg, -1 bei Fehler
 * */

int create_client_socket(char **args) {
	char *host = args[1];
	char *port = args[2];

	// Adressen für Filter, Adress-Familie und Connection-Adresse
	struct addrinfo *addr_fam, *addr_con, hints;

	// Filter für potentielle Verbindungen erstellen
	// Setze Filter für passende Verbindungen
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = 0;

	// Adressauflösung, speichere Adressen in addr_fam, hints sind Filter
	int err = getaddrinfo(host, port, &hints, &addr_fam);
	// Fehler der Adressauflösung abfangen
	if (err) {
		debugPrint("getaddrinfo: %s\n", gai_strerror(err));
		return (-1);
	}

	// Speichere gefundene Adress-Familie in Connection-Variable
	addr_con = addr_fam;

	// Socket
	int sock;

	// Versuche für alle Adressen der Adress-Familie eine Verbindung herzustellen
	// Beende Schleife, falls Verbindung erfolgreich.
	while (addr_con) {

		// Erzeuge Socket für aktuelle Adresse
		sock = socket(addr_con->ai_family, addr_con->ai_socktype, 0);

		//Fehler der Socketerstellung abfangen
		if (sock == -1) {
			debugPrint("Socket konnte nicht erstellt werden...\n");
			return (-1);
		}

		// String Namensauflösung der Zieladresse
		char dest[INET_ADDRSTRLEN];

		// Hole Name der Socketadresse
		getnameinfo(addr_con->ai_addr, addr_con->ai_addrlen, dest, sizeof(dest), NULL, 0, NI_NUMERICHOST);

		debugPrint("Verbindungsaufbau zu %s ... \n", dest);

		// Versuche Socket-Verbindung bei aktuell gewählter Adresse
		if (connect(sock, addr_con->ai_addr, addr_con->ai_addrlen) == 0) {
			debugPrint("Verbunden!\n");
			// Steige aus While-Schleife für Connection-Versuch aus
			// Gebe verbundenen Socket zurück
			return (sock);
		}

		// Socket Verbindung bei aktueller Adresse konnte nicht aufgebaut werden
		else {
			debugPrint("Verbindung gescheitert...\n");
			//Schliesse Socket nach gescheitertem Verbindungsversuch
			close(sock);
			// Wähle nächste Adresse
			addr_con = addr_con->ai_next;
		}
	}
	// Falls zu keiner Adresse verbunden werden konnte
	return -1;
}

/**
 * \brief	Erzeugt Listener-Socket
 *
 * \param 	port enthält Port auf dem Socket hören soll
 *
 * \return	Socket bei Erfolg, -1 bei Fehler
 * */

int create_server_socket(unsigned short port) {

	// Erstelle Socket mit TCP/IPv4 Protokoll
	errno = 0;
	int login_socket = socket(AF_INET, SOCK_STREAM, 0);

	//Fehler beim Socketerstellen abfangen
	if (login_socket == -1) {
		perror("Listener Socket konnte nicht erstellt werden: ");
		return -1;
	}

	//Portnummer freigeben, nachdem Socket geschlossen wurde
	int reuse = 1;
	setsockopt(login_socket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

	// Addressstruktur für Socketadresse
	struct sockaddr_in address;

	// Addresseigenschaften festlegen
	memset(&address, 0, sizeof(address)); /* initialize address structure with zeroes */
	address.sin_family = AF_INET; /* IPv4 address */
	address.sin_port = htons(port); /* port has to be in network byte order! */
	address.sin_addr.s_addr = INADDR_ANY; /* listen on all interfaces */

	/* bind socket to the address initialized above */
	errno = 0;
	if (bind(login_socket, (struct sockaddr *) &address, sizeof(address)) == -1) {
		perror("bind error:");
		close(login_socket);
		return -1;
	}

	/* make it a passive socket that just waits for connections */
	errno = 0;
	if (listen(login_socket, SOMAXCONN) == -1) {
		perror("listen error: ");
		close(login_socket);
		return -1;
	}

	infoPrint("Serverport: %d\n", port);
	return login_socket;
}

/**
 * \brief	Sendet RFC-Message an sock
 *
 * \param 	type RFC-Typ,
 * \param 	length Länge der Nutzdaten in Byte,
 * \param 	msg_data Pointer auf Nutzdaten
 *
 * \return	Bei Erfolg: Anz. ges. Bytes, bei Fehler: Error-Code (RFC)
 * */

int send_MSG(int sock, uint8_t type, uint16_t length, void* msg_data) {

	// Wandle length in Network-Byte-Order
	//uint16_t length_swap = htons(length);

	// snd = Zähler-/Fehlervariable für send()
	// total = Zählervariable für gesendete Bytes
	int snd; //, total = 0;

	// Alles in Einem packen und senden
	struct RFC_MSG rfcMsg;
	rfcMsg.header.type = type;
	rfcMsg.header.length = htons(length);
	memcpy(rfcMsg.data, msg_data, length);

	// Sende RFC-Typ auf sock
	errno = 0;
	//snd = send(sock, &type, sizeof(type), 0);
	snd = send(sock, &rfcMsg, HEADER_LENGTH + length, 0);

	// Fange Fehler von send() ab
	if (snd == 0) {

		// Socket wurde von der Gegenseite geschlossen
		if (errno == 0) {

			debugPrint("Senden der Nachricht vom Typ %d fehlgeschlagen", type);
			debugPrint("Socket wurde von Gegenseite geschlossen\n");
			return SOCKET_ERROR;

		} else {
			return UNKNOWN_ERROR;
		}
	}

	// send()-Error
	if (snd < 0) {
		debugPrint("Senden der Nachricht vom Typ %d fehlgeschlagen\n", type);
		return SEND_ERROR;
	}

	return snd;

}

/**
 * \brief	Empfange RFC-Message von sock
 *
 * \param	msg_data Zeiger auf Message
 *
 * \return	Bei Erfolg: Message-Typ, Bei Fehler: Error-Code(RFC)
 * */
int receive_MSG(int sock, struct RFC_MSG *msg_data) {

	int rec, total = 0;

	// Setze errno auf 0, damit Fehler erkannt wird
	errno = 0;
	// Empfange Header der RFC-Message und schreibe
	// in Header von msg_data (MSG_WAITALL = POTENTIELLER DEADLOCK)
	rec = recv(sock, &msg_data->header, HEADER_LENGTH, MSG_WAITALL);

	// Fehler abfangen
	if (rec == 0) {

		// Socket wurde von der Gegenseite geschlossen
		if (errno == 0) {

			debugPrint("Empfangen einer Nachricht von Socket %d fehlgeschlagen", sock);
			debugPrint("Socket wurde von Gegenseite geschlossen\n");

			return SOCKET_ERROR;

		} else {
			return UNKNOWN_ERROR;
		}
	}

	// recv()-Error
	if (rec < 0) {
		debugPrint("Empfangen einer Nachricht von Socket %d fehlgeschlagen\n", sock);
		return RECEIVE_ERROR;
	}

	// Falls RFC-Typ unbekannt
	if (msg_data->header.type > 12 && msg_data->header.type != 255) {
		return (RFC_UNKNOWN);
	}

	// Addiere empfangene Bytes auf bisher empfangene Bytes
	total = total + rec;

	// Drehe length (Länge der Nutzdaten in Byte) von Network-Byte Order
	// zu Host-Byte-Order
	msg_data->header.length = ntohs(msg_data->header.length);

	// Wenn Message Nutzdaten enthält
	if (msg_data->header.length > 0) {

		// Reserviere Speicher für Nutzdaten

		// Setze errno auf 0, damit Fehler erkannt wird
		errno = 0;

		// Nutzdaten empfangen (MSG_WAITALL = POTENTIELLER DEADLOCK)
		rec = recv(sock, msg_data->data, msg_data->header.length, MSG_WAITALL);

		// Fehler abfangen
		if (rec == 0) {

			// Socket wurde von der Gegenseite geschlossen
			if (errno == 0) {

				debugPrint("Empfangen einer Nachricht von Socket %d fehlgeschlagen", sock);
				debugPrint("Socket wurde von Gegenseite geschlossen\n");
				return SOCKET_ERROR;

			} else {
				return UNKNOWN_ERROR;
			}
		}

		// recv()-Error
		if (rec < 0) {
			debugPrint("Empfangen einer Nachricht von Socket %d fehlgeschlagen\n", sock);
			return RECEIVE_ERROR;
		}

		// Addiere empfangene Bytes auf bisher empfangene Bytes
		total = total + rec;
	}

	// Ausgabe bei Erfolg des Empfangs
	debugPrint("Nachricht von Typ %d mit %d Byte Gesamtlänge wurde erfolgreich empfangen\n", msg_data->header.type, total);
	// Gebe MSG-Typ zurück
	return msg_data->header.type;
}

/**
 * \brief	Sende Error-Message an Client
 *
 * \param	client_sock Socket an den MSG geschickt wird
 * \param	subtype Error-Subtype FATAL oder WARNING (RFC)
 * \param	err_txt zu sendender Error-Text
 *
 * \return	Bei Erfolg: anz. ges. Bytes, Bei Fehler: Err-Code (RFC)
 * */
int send_err_MSG(int client_sock, uint8_t subtype, char* err_txt) {

	// Hole Speicher für Subtype + Beschreibung
	char* err_msg = (char*) malloc(strlen(err_txt) + sizeof(uint8_t));

	// Kopiere Subtyp in Error-Msg-Daten
	memcpy(err_msg, &subtype, sizeof(uint8_t));

	// Kopiere Fehlerbeschreibung in Error-Msg-Daten
	memcpy(err_msg + 1, err_txt, 1 + strlen(err_txt));

	// Sende Error-Msg an Client
	int snd;
	snd = send_MSG(client_sock, ERROR_WARNING, strlen(err_txt) + sizeof(uint8_t), err_msg);

	// Gebe allokierten Speicher wieder frei
	free(err_msg);
	err_msg = NULL;

	// Kehre mit Anzahl gesendeter Bytes oder Fehler-Code zurück
	return snd;
}

/**
 * \brief	Prüfe bei Senden/Empfangen auf Fehler
 *
 * \param	com_ret Rückgabewert einer Send/Receive-Funktion
 *
 * \return	Bei Erfolg com_ret, Bei Fehler: COM_ERR-Code (RFC)
 * */
int catch_com_err(int com_ret) {

	switch (com_ret) {

	// Wenn beim Senden ein Socket Error auftritt
	case SOCKET_ERROR:
		debugPrint("Socket wurde geschlossen\n");
		return COM_ERR;
		break;

		// Wenn beim Senden ein send()-Error auftritt
	case SEND_ERROR:
		debugPrint("Nachricht konnte nicht geschickt werden\n");
		return COM_ERR;
		break;

	case RECEIVE_ERROR:
		debugPrint("Nachricht konnte nicht empfangen werden\n");
		return COM_ERR;
		break;

		// Unknown Error
	case UNKNOWN_ERROR:
		debugPrint("Ein unbekannter Fehler ist aufgetreten\n");
		return COM_ERR;
		break;

	case RFC_UNKNOWN:
		debugPrint("Ein unbekanntes RFC-Paket wurde empfangen\n");
		return COM_ERR;
		break;

	default:
		break;
	}

	return com_ret;
}

/**
 * \brief	Prüfe ob Socket von Gegenseite geschlossen wurde
 *
 * \param	sock zu prüfender Socket
 *
 * \return	Bei offenem Socket: 0, bei geschlossenem Socket: -1
 * */
int connection_check(int sock) {

	char buffer[2];

	if(recv(sock, buffer, sizeof(buffer), MSG_DONTWAIT | MSG_PEEK) == 0) {
		debugPrint("Connection Check: Socket %d wurde von Gegenseite geschlossen...\n", sock);
		return -1;
	}
	debugPrint("Connection Check: Socket %d offen...\n", sock);
	return 0;
}
